package merlin.managers;

import java.io.*;
import java.sql.*;
import java.util.*;
import java.text.*;

import merlin.beans.*;
import merlin.dao.*;
import merlin.utils.*;

/**
 * A Runnable component which generates a mailing list based on the provided criteria.
 * The class implements Runnable so that it can be run on a separate thread as it takes
 * a substancial amount of time to complete its task if the number of records to be handled
 * is large.
 *
 * Future enhancements may require a change in the structure of the code to convert it into
 * a cancellable task, in case it is taking up too much resources and hampering the activities
 * of the other parts of the web application.
 *
 * @author	Sandip Bhattacharya
 */
public class MailingListGenerator implements Runnable{


	private ReportTitlesBean[] arrReports = null;	// cache the report titles.

	private boolean selectYoungest;
	private int reportNumber;
	private int recordLimit;


	public MailingListGenerator(boolean selectYoungest,int reportNumber,int recordLimit){
    	this.selectYoungest = selectYoungest;
    	this.reportNumber = reportNumber;
    	this.recordLimit = recordLimit;
	}

	public void run(){
		long start = System.currentTimeMillis();

		generateMailingList(selectYoungest,reportNumber,recordLimit);

		System.out.println("time taken : " + (System.currentTimeMillis() - start));
	}

	/**
	 * Main function that controls the mailing list generation.
	 */
	public void generateMailingList(boolean selectYoungest,int reportNumber,int recordLimit){
		ConnectionPool poolWebsite = null, poolLookup = null;
		Connection conWebsite = null, conLookup = null;

		Map mapMailingLists = null;

		try{

			SimpleDateFormat sdf = new SimpleDateFormat("dd_MMM_yyyy_hh_mm_ss_a");
			String time = sdf.format(new java.util.Date());
			String fileName = ApplicationResources.getInstance().getProperty("iwSummaryPDFStorageFolder") + "MailingList-" + time + ".txt";
			String rejectedListfileName = ApplicationResources.getInstance().getProperty("iwSummaryPDFStorageFolder") + "RejectedList-" + time + ".txt";

			List rejectedMailingListEntries = new LinkedList();

			poolWebsite = ConnectionPool.getConnectionPool("website");
			conWebsite = poolWebsite.getConnection();

			poolLookup = ConnectionPool.getConnectionPool("lookup");
			conLookup = poolLookup.getConnection();

			CompanyWebsiteDetailsDAO cwddao = new CompanyWebsiteDetailsDAO(conWebsite);
			DirectorDetailsDAO dddao = new DirectorDetailsDAO(conLookup);
			ReportTitlesDAO rtdao = new ReportTitlesDAO(conWebsite);

			log("getting reports for caching...");
			arrReports = rtdao.getAllReports(); // load and cache the report titles.
			log("got all report names");

			// make an entry into the email listing history table.
			String description = null;
			if(selectYoungest){
				description = "Youngest Male Director Report " + reportNumber;
			}else{
				description = "Oldest Male Director Report " + reportNumber;
			}

			log("adding new entry in MSAL_EMAIL_LIST_DOWNLOAD_HISTORY...");
			int newEmailListingId = cwddao.addEmailListGenerationEntry(description);
			log("added new entry in MSAL_EMAIL_LIST_DOWNLOAD_HISTORY");

			log("getting mailing list VOs...");
			mapMailingLists = cwddao.getMailingListVOs(recordLimit);
			log("got the mailing list VOs");

			System.out.println("Starting processing of " + mapMailingLists.size() + " records.");

			Iterator iter = mapMailingLists.keySet().iterator();

			while(iter.hasNext()){
				String registeredNumber = (String)iter.next();
				log("beginning processing for company " + registeredNumber + "...");
				// check the validity of the email. In case it is incorrect, dump it into a rejected list.
                MailingListVO mailingBean = (MailingListVO)mapMailingLists.get(registeredNumber);
				if(! MerlinUtilities.isValidEmail(mailingBean.getEmail())){
					rejectedMailingListEntries.add(mailingBean);
					iter.remove();
					continue;
				}

				log("getting director for company id " +  registeredNumber + " ...");
				DirectorDetailsBean dirBean = dddao.getFWBSDirectorByCompanyNumber(registeredNumber,selectYoungest);
				log("got director for company id " +  registeredNumber);

				if(dirBean != null){
					//MailingListVO mailingBean = (MailingListVO)mapMailingLists.get(registeredNumber);
					mailingBean.setCompanyName(dirBean.getCompanyName());
					mailingBean.setDirectorFirstName(dirBean.getFirstname());
					mailingBean.setDirectorSurname(dirBean.getSurname());
					mailingBean.setDirectorPrefix(dirBean.getPrefix());

					// possible optimization : load the report titles (full details) only once,
					// and for each report info per registered number, fetch only the report ids.
					log("getting reports for company " + registeredNumber + " ...");
					List reportTitles = rtdao.getReportIdsByCompanyNumber(registeredNumber);
					log("got reports for company " + registeredNumber);
					if(reportTitles.size() > 0){
						log("selecting correct report for " + registeredNumber + "...");
	                    ReportTitlesBean reportBean = selectReport(reportTitles,reportNumber);
						log("selected correct report for " + registeredNumber);
						if(reportBean != null){
		                    log("setting report values for the mailing bean...");
		                    mailingBean.setReportTitleId(reportBean.getReportTitlesId());
							mailingBean.setReportTitle(reportBean.getReportTitle());
		                    mailingBean.setNoOfCompaniesInBook(reportBean.getNoOfCompaniesInBook());
		                    mailingBean.setNoOfCompaniesInDB(reportBean.getNoOfCompaniesInDatabase());
		                    mailingBean.setReportCode(reportBean.getReportCode());
		                    log("set report values for the mailing bean");
						}else{
							// the report ids found in the query don't correspond to any actual report in REPORT_TITLES.
							// remove this entry from the main map.
							iter.remove();
						}
					}else{
						// no report exists for this entry. Remove from the main map.
						iter.remove();
					}
				}else{
					// no director exists for this entry. Remove from the main map.
					iter.remove();
				}
				log("finished processing for company " + registeredNumber);
			}

			// save the mailing list.
			// first sort on report number.
			List mailingList = new LinkedList(mapMailingLists.values());
			Collections.sort(mailingList,new MailingListVOComparator());

			log("saving mailing list to file...");
			System.out.println("Saving " + mailingList.size() + " records to file " + fileName);
			ByteArrayOutputStream bOut = new ByteArrayOutputStream();
			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(bOut));
			saveDataToWriter(mailingList,writer);
			log("saved mailing list to file");

			// save the rejected list.
			log("saving rejected list to file...");
			System.out.println("Saving " + mapMailingLists.size() + " records to file " + rejectedListfileName);
			BufferedWriter writerRejectedList = new BufferedWriter(new FileWriter(rejectedListfileName));
			saveDataToWriter(rejectedMailingListEntries,writerRejectedList);
			log("saved rejected list to file");

			System.out.println("Saved list to " + fileName);
			System.out.println("Saved rejected list to " + rejectedListfileName);

			//save the list into the database.
			log("adding the email list to database...");
			byte[] data = bOut.toByteArray();
			cwddao.addEmailListing(newEmailListingId,data);
			log("added the email list to database");
			System.out.println("email list saved into database");

		}catch(Exception e){
			e.printStackTrace();
		}finally{
			poolWebsite.returnConnection(conWebsite);
			conWebsite = null;

			poolLookup.returnConnection(conLookup);
			conLookup = null;
		}
	}


    /**
     * Select the topmost possible report from the list of reports based on the reportNumber.
     */
	private ReportTitlesBean selectReport(List reportTitles,int reportNumber){
		ReportTitlesBean bean = null;
		/*
			1. Determine the cutoff point.
				The cutoff point is the lesser of the report number and
				the size of the	report ids found for this company.

			2. From the cutoff point onwards, loop backwards in the reportTitles list, and return the
				first non-null report found. The report ids in the reportTitles list may contain values

		*/
		int cutoffPoint = Math.min(reportNumber,reportTitles.size());

		for(int i=cutoffPoint-1;i>=0;i--){
			int reportId = ((Integer)reportTitles.get(i)).intValue();
			if(reportId < arrReports.length){
				bean = arrReports[reportId];
				if(bean != null){
					break;
				}
			}
		}

		return bean;
	}


	/**
	 * Save the generated mailing list to a persistent storage.
	 * TODO : Store in a table ?
	 */
	private void saveDataToWriter(Collection mapMailingList, Writer out){


		try{
        	Iterator iter = mapMailingList.iterator();
        	StringBuilder builder = new StringBuilder();
        	while(iter.hasNext()){
        		MailingListVO mailingBean = (MailingListVO)iter.next();
				builder.append(mailingBean.getDirectorFirstName() + " " + mailingBean.getDirectorSurname());
				builder.append(", Director");
				builder.append('\t');
				builder.append(mailingBean.getDirectorFirstName());
				builder.append('\t');
				builder.append(mailingBean.getDirectorSurname());
				builder.append('\t');
				builder.append(mailingBean.getCompanyName());
                builder.append('\t');
                builder.append(mailingBean.getEmail());
                builder.append('\t');
                builder.append(mailingBean.getDirectorPrefix());
                builder.append('\t');
                builder.append(mailingBean.getReportTitle());
                builder.append('\t');
                builder.append(mailingBean.getReportTitleId());
                builder.append('\t');
                builder.append(mailingBean.getNoOfCompaniesInDB());
                builder.append('\t');
                builder.append(mailingBean.getCompanyRegisteredNumber());
                builder.append('\t');
                builder.append(mailingBean.getNoOfCompaniesInBook());
                builder.append('\t');
                builder.append(mailingBean.getReportCode());
                builder.append('\n');
                out.write(builder.toString());
                builder.delete(0,builder.length());
        	}
			out.close();
		}catch(Exception e){
        	e.printStackTrace();
		}
	}

	private static long lastCall = 0;
	private static void log(Object msg){
		long currentCall = System.currentTimeMillis();
		System.out.println(currentCall + "[" + (currentCall - lastCall) + "] : " + msg);
		lastCall = currentCall;
	}

	static class MailingListVOComparator implements Comparator{
		public int compare(Object o1,Object o2){
			MailingListVO first = (MailingListVO)o1;
			MailingListVO second = (MailingListVO)o2;

			return first.getReportTitleId() - second.getReportTitleId();
		}

		public boolean equals(Object obj){
			return false;
		}
	}
}